import { useGlobSetting } from "@/hooks";
import { circle, point, sector, type Units } from "@turf/turf";
import { merge } from "lodash-es";
import * as mars3d from "mars3d";
import {
  Cartesian2,
  Cartesian3,
  Cartographic,
  Math as CesiumMath,
  Ellipsoid,
  ImageryLayer,
  Matrix4,
  SceneMode,
  Transforms,
  UrlTemplateImageryProvider,
  Viewer,
  WebMercatorProjection,
} from "mars3d-cesium";

const { sin, cos, asin, atan2, sqrt, PI: π } = Math;
const { toDegrees: deg, toRadians: rad } = CesiumMath;

/** 地球半径，米 */
export const R = 6371393;
const r0 = 6356752.3142451793;
const r1 = 6378137.0;

export type Mars3DMapOptions = {
  scene?: mars3d.Map.sceneOptions;
  terrain?: mars3d.Map.terrainOptions;
  basemaps?: mars3d.Map.basemapOptions[];
  layers?: mars3d.Map.layerOptions[];
  control?: mars3d.Map.controlOptions;
  effect?: mars3d.Map.effectOptions;
  thing?: mars3d.Map.thingOptions;
  mouse?: mars3d.Map.mouseOptions;
  method?: mars3d.Map.methodOptions;
};

export function zeroFill(e: number, t: number, i: number) {
  return e.toString(i || 10).padStart(t, "0");
}

export function useImageryLayer() {
  const { layerUrl, layerMaxLevel } = useGlobSetting();
  if (!layerUrl) return undefined;

  const e = { url: layerUrl, maximumLevel: (layerMaxLevel || 5) as number };
  const i = e.url;
  const n = e.maximumLevel;
  return new ImageryLayer(
    new UrlTemplateImageryProvider({
      subdomains: ["1", "2", "3"],
      url: "".concat(i, "/{sx}/{sy}/{sz}.jpg"),
      customTags: {
        sx: (e: number, t: number, i: number, n: number) => "L" + zeroFill(n, 2, 10).toUpperCase(),
        sy: (e: number, t: number, i: number) => "R" + zeroFill(i, 8, 16).toUpperCase(),
        sz: (e: number, t: number) => "C" + zeroFill(t, 8, 16).toUpperCase(),
      },
      maximumLevel: n,
    }),
    {},
  );
}

export function getViewer(container: string | Element, options?: Viewer.ConstructorOptions | undefined) {
  return new Viewer(
    container,
    Object.assign(
      {
        baseLayer: useImageryLayer(),
        animation: !0,
        timeline: !0,
        shouldAnimate: false,
        fullscreenButton: !1,
        geocoder: !1,
        homeButton: !1,
        sceneModePicker: !1,
        scene3DOnly: !1,
        selectionIndicator: !1,
        infoBox: !1,
        baseLayerPicker: !1,
        navigationHelpButton: !1,
        vrButton: !1,
        shadows: !1,
        mapProjection: new WebMercatorProjection(Ellipsoid.WGS84),
        sceneMode: SceneMode.SCENE2D,
      },
      options,
    ),
  );
}

export function screen2coordinate(viewer: Viewer, screen: Cartesian2, mode: "ellipsoid" | "scene" | "terrain" = "ellipsoid") {
  let c3: Cartesian3 | undefined;
  let ct: Cartographic | undefined;
  let ray = viewer.camera.getPickRay(screen);

  switch (mode) {
    case "scene":
      c3 = viewer.scene.pickPosition(screen);
      break;
    case "terrain":
      ray && (c3 = viewer.scene.globe.pick(ray, viewer.scene));
      break;
    case "ellipsoid":
      c3 = viewer.camera.pickEllipsoid(screen, viewer.scene.globe.ellipsoid);
  }

  if (c3) ct = Cartographic.fromCartesian(c3);
  return { c3, ct };
}

/** 半正矢公式（Haversine Formula）计算距离，米
 * @param λ1 点1经度，弧度
 * @param φ1 点1纬度，弧度
 * @param λ2 点2经度，弧度
 * @param φ2 点2纬度，弧度
 */
export function haversine(λ1: number, φ1: number, λ2: number, φ2: number) {
  const Δφ = φ2 - φ1;
  const Δλ = λ2 - λ1;
  const a = sin(Δφ / 2) * sin(Δφ / 2) + cos(φ1) * cos(φ2) * sin(Δλ / 2) * sin(Δλ / 2);
  const c = 2 * atan2(sqrt(a), sqrt(1 - a));
  return R * c;
}

/**
 * 计算方位角，从北顺时针开始，弧度
 * @param λ1 起点经度，弧度
 * @param φ1 起点纬度，弧度
 * @param λ2 终点经度，弧度
 * @param φ2 终点纬度，弧度
 */
export function bearing(λ1: number, φ1: number, λ2: number, φ2: number) {
  const Δλ = λ2 - λ1;
  const y = sin(Δλ) * cos(φ2);
  const x = cos(φ1) * sin(φ2) - sin(φ1) * cos(φ2) * cos(Δλ);
  const θ = atan2(y, x);
  return θ < 0 ? θ + 2 * π : θ;
}

/**
 * 根据条件计算终点坐标，弧度
 * @param λ1 起点经度，弧度
 * @param φ1 起点纬度，弧度
 * @param β 方位角，从北顺时针开始，弧度
 * @param δ 行进距离，米
 */
export function destination(λ1: number, φ1: number, β: number, δ: number) {
  const φ2 = asin(sin(φ1) * cos(δ / R) + cos(φ1) * sin(δ / R) * cos(β));
  const λ2 = λ1 + atan2(sin(β) * sin(δ / R) * cos(φ1), cos(δ / R) - sin(φ1) * sin(φ2));
  return [λ2, φ2];
}

/**
 * 获取扇形点集
 * @param center 扇心
 * @param radius 半径
 * @param bearing1 边界方位1
 * @param bearing2 边界方位2
 * @param units 单位，米
 * @returns
 */
export function getSector(center: Cartographic, radius: number, bearing1: number, bearing2: number, units: Units = "meters") {
  const c = point([deg(center.longitude), deg(center.latitude)]);
  const s = sector(c, radius, bearing1, bearing2, { units });
  return s.geometry.coordinates[0].map((c) => Cartesian3.fromDegrees(c[0], c[1]));
}

/**
 * 获取圆点集
 * @param center 圆心
 * @param radius 半径
 * @param units 单位，米
 * @returns
 */
export function getCircle(center: Cartographic, radius: number, units: Units = "meters") {
  const p = point([deg(center.longitude), deg(center.latitude)]);
  const c = circle(p, radius, { units });
  return c.geometry.coordinates[0].map((c) => Cartesian3.fromDegrees(c[0], c[1]));
}

export function getMap(container: string | Viewer, options?: Mars3DMapOptions) {
  const { layerUrl, layerMaxLevel } = useGlobSetting();
  const map = new mars3d.Map(
    container,
    merge(
      {
        controls: { homeButton: true },
        scene: {
          center: { lng: 121.5, lat: 23.5, alt: 2e5, heading: 0, pitch: -90 },
          globe: { depthTestAgainstTerrain: true },
        },
      },
      options,
    ),
  );

  const tileLayer = new mars3d.layer.XyzLayer({
    url: `${layerUrl}/L{sx}/R{sy}/C{sz}.jpg`,
    customTags: {
      sx: (_: mars3d.layer.XyzLayer, _z: number, _y: number, x: number) => x.toString().padStart(2, "0").toUpperCase(),
      sy: (_: unknown, _z: number, y: number) => y.toString(16).padStart(8, "0").toUpperCase(),
      sz: (_: unknown, z: number) => z.toString(16).padStart(8, "0").toUpperCase(),
    },
    maximumLevel: +layerMaxLevel || 5,
  });
  map.addLayer(tileLayer);

  return map;
}

/**
 * 将一个点按切面平移，坐标系为东-北-上
 * @param point 球面点
 * @param vetor 移动向量
 * @returns 球面点沿切面移动后的点
 */
export function translate(point: Cartesian3, vetor: Cartesian3) {
  const m4 = Transforms.eastNorthUpToFixedFrame(point);
  return Matrix4.multiplyByPoint(m4, vetor, new Cartesian3());
}

/**
 * 生成球面点集
 * @param center 中心点
 * @param radius 半径
 * @param count 球面点数量
 * @param clip 是否裁剪，裁剪掉高度小于0的点
 */
export function generatePoints(center: Cartesian3, radius: number, count: number, clip: boolean = false) {
  const points: Cartesian3[] = [];
  const ratio = (Math.sqrt(5) - 1) / 2; // 黄金比例，球面点分布方式

  for (let n = 1; n <= count; n++) {
    const zn = (2 * n - 1) / count - 1;
    const xn = Math.sqrt(1 - zn * zn) * Math.cos(2 * Math.PI * n * ratio);
    const yn = Math.sqrt(1 - zn * zn) * Math.sin(2 * Math.PI * n * ratio);
    const vn = new Cartesian3(xn * radius, yn * radius, zn * radius);
    const point = translate(center, vn);
    if (clip) {
      const cg = Cartographic.fromCartesian(point);
      if (cg.height < 0) continue;
    }
    points.push(point);
  }
  return points;
}
